package pl.edu.agh.agents.gui;

import java.awt.Color;
import java.awt.Point;
import java.awt.Toolkit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import pl.edu.agh.agents.agent.Agent;
import pl.edu.agh.agents.path.Path;
import pl.edu.agh.agents.state.State;

import com.mxgraph.model.mxCell;
import com.mxgraph.util.mxConstants;
import com.mxgraph.view.mxGraph;

/**
 * drawing, autolayout and node coloring for mxGraph
 * @author Kuba
 *
 */
public class GraphUtils {
	public static final int TOOLBAR_WIDTH = 200;
	public static final int GRAPH_WIDTH = (int) (Toolkit.getDefaultToolkit().getScreenSize().getWidth() * 0.75) - TOOLBAR_WIDTH;
	public static final int GRAPH_HEIGHT = (int) (Toolkit.getDefaultToolkit().getScreenSize().getHeight() * 0.75);
	public static final int AGENT_WIDTH = 24;
	public static final int AGENT_HEIGHT = 24;
	public static final Color DEFAULT_AGENT_COLOR = new Color(255, 0, 0);

	private List<Point> coords;
	private static GraphUtils instance = new GraphUtils();

	private GraphUtils() {}

	public static GraphUtils getInstance() {
		return instance;
	}

	public void setCoords(List<Point> coords) {
		this.coords = coords;
	}

	public void drawInitialConfiguration(GUI gui) {
		mxGraph graph = gui.getGraph();
		List<State> states = gui.getStates();
		Map<mxCell, State> cellsStates = gui.getCellsStates();

		graph.getModel().beginUpdate();

		try {
			for (State state : states) {
				Point point = coords.get(state.getID());
				mxCell cell = (mxCell) graph.insertVertex(
						graph.getDefaultParent(), null, state.getID(),
						point.getX(), point.getY(), AGENT_WIDTH, AGENT_HEIGHT);

				if (state.isSink()) {
					cell.setStyle("shape=ellipse");
				}
				cellsStates.put(cell, state);
			}

			for (State state : states) {
				for (State neighbor : state.getNeighbors()) {
					mxCell cell = gui.getCellForState(state);
					mxCell neighborCell = gui.getCellForState(neighbor);
					graph.insertEdge(graph.getDefaultParent(), null, "", cell, neighborCell);
				}

				colorStates(gui);
			}
		} finally {
			graph.getModel().endUpdate();
		}
	}

	private String colorToString(Color color) {
		String rgb = Integer.toHexString(color.getRGB());
		return rgb.substring(2);
	}

	public void colorStates(GUI gui) {
		mxGraph graph = gui.getGraph();
		List<State> states = gui.getStates();

		graph.getModel().beginUpdate();
		for (State state : states) {
			Color color = new Color (255, 255, 255);
			Color fontColor = new Color(0, 0, 0);
			Agent agent = state.getAgent();

			if (agent != null) {
				if (agent.getCurrentState() == agent.getGoalState()) {
					color = new Color(0, 0, 0);
					fontColor = new Color(255, 255, 255);
				} else {
					color = gui.getAgentColor(agent);
				}
			}

			Object[] cells = new Object[] {gui.getCellForState(state) };
			graph.setCellStyles(mxConstants.STYLE_FILLCOLOR, colorToString(color), cells);
			graph.setCellStyles(mxConstants.STYLE_FONTCOLOR, colorToString(fontColor), cells);
		}
		graph.getModel().endUpdate();

		graph.refresh();
	}

	public void highlightAll(GUI gui) {
		mxGraph graph = gui.getGraph();

		graph.getModel().beginUpdate();
		Object[] edges = graph.getChildEdges(graph.getDefaultParent());

		// set all edges to visible
		for (Object edge : edges) {
			graph.setCellStyles(mxConstants.STYLE_OPACITY, "100", new Object[] { edge });
		}

		graph.getModel().endUpdate();
		return;
	}

	public void highlightPath(GUI gui, Agent agent) {
		mxGraph graph = gui.getGraph();

		graph.getModel().beginUpdate();

		// all edges in graph
		Object[] edges = graph.getChildEdges(graph.getDefaultParent());

		// set all edges to visible
		for (Object edge : edges) {
			graph.setCellStyles(mxConstants.STYLE_OPACITY, "100", new Object[] { edge });
		}

		// edges which shouldn't be set to almost transparent
		// if path is not null they are edges on path, otherwise - all edges in graph
		List<mxCell> visibleEdges = new ArrayList<mxCell>();

		Path path = null;

		if (agent != null) {
			path = agent.getPath();
		}
		
		if (path == null) {
			path = new Path();
		}

		List<State> pathStates = path;
		for (int i = 0; i < pathStates.size() - 1; i++) {
			State s1 = pathStates.get(i);
			State s2 = pathStates.get(i+1);

			mxCell c1 = gui.getCellForState(s1);
			mxCell c2 = gui.getCellForState(s2);

			// get edge through which agent will walk
			Object[] edgesBetween = graph.getEdgesBetween(c1, c2);

			for (Object o : edgesBetween) {
				mxCell edge = (mxCell)o;
				// we want only edges from c1 to c2, not from c2 to c1
				if (edge.getSource().equals(c1)) {
					visibleEdges.add(edge);
				}
			}
		}

		for (Object edge : edges) {
			if (!visibleEdges.contains(edge)) {
				// set all edges that are not on path to almost transparent
				graph.setCellStyles(mxConstants.STYLE_OPACITY, "10", new Object[] { edge });
			}
		}

		graph.getModel().endUpdate();
	}
}